// Copyright 2010, Vanya Davidenko.
// Используемая кодировка: UTF-8.
// Copyright 2000, Jeffrey Richter (Windows ReadWrite mutex).

#ifndef LIBV_LOCK_MUTEX_H_
#define LIBV_LOCK_MUTEX_H_

#ifdef _WIN32
# include <libv/windows_lean.h>
#else
# include <errno.h>
# include <stdlib.h>
# include <pthread.h>
#endif

#include <libv/check.h>
#include <libv/lock.h>
#include <libv/lock_spin.h>


namespace libv
{

#ifdef _WIN32
typedef CRITICAL_SECTION MutexType;
#else
typedef pthread_mutex_t MutexType;
#endif


// Не предназначен для использования.
// Необходим для устранения дублирования.
// Рекурсивному мютексу необходима особая инициализация, которая реализована
// через private-наследование, поэтому член mutex_ объявлен protected.
class MutexImpl
{
  public:
    inline void DefaultInit(const uint32_t num_spins = 4000);
    inline void DefaultDestroy();

    inline void Lock();
    inline bool TryLock();
    inline void Unlock();

  protected:
    MutexType mutex_;
#ifndef _WIN32
    uint32_t num_spins_;  // эмуляция SetCriticalSectionSpinCount в Linux
#endif
};


#ifdef _WIN32

void MutexImpl::DefaultInit(const uint32_t num_spins /*= 4000*/)
{
    VERIFY(::InitializeCriticalSectionAndSpinCount(&mutex_,num_spins) != FALSE);
}

void MutexImpl::DefaultDestroy()
{
    ::DeleteCriticalSection(&mutex_);
}

void MutexImpl::Lock()
{
    ::EnterCriticalSection(&mutex_);
}

bool MutexImpl::TryLock()
{
    return ::TryEnterCriticalSection(&mutex_) != FALSE;
}

void MutexImpl::Unlock()
{
    ::LeaveCriticalSection(&mutex_);
}


#else


void MutexImpl::DefaultInit(const uint32_t num_spins /*= 4000*/)
{
    VERIFY(::pthread_mutex_init(&mutex_, NULL) == 0);
    num_spins_ = num_spins;
}

void MutexImpl::DefaultDestroy()
{
    VERIFY(::pthread_mutex_destroy(&mutex_) == 0);
}

void MutexImpl::Lock()
{
    for ( uint32_t spin_num = 0 ; spin_num < num_spins_ ; ++spin_num )
    {
        if ( TryLock() )
            return;
    }

    VERIFY(::pthread_mutex_lock(&mutex_) == 0);
}

bool MutexImpl::TryLock()
{
    const int result = ::pthread_mutex_trylock(&mutex_);
    if ( result == 0 )
        return true;

    if ( result == EBUSY )
        return false;

    // Не должно достигаться.
    VERIFY(result == 0 || result == EBUSY);
    return false;
}

void MutexImpl::Unlock()
{
    VERIFY(::pthread_mutex_unlock(&mutex_) == 0);
}

#endif  // _WIN32


////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////


/** Переносимый нерекурсивный мьютекс. */
class Mutex : public LockableResourceInterface
{
  public:
    Mutex()
    {
       mutex_impl_.DefaultInit();
    }

    ~Mutex()
    {
        mutex_impl_.DefaultDestroy();
    }

    /*virtual*/ void Lock()
    {
        return mutex_impl_.Lock();
    }

    /*virtual*/ bool TryLock()
    {
        return mutex_impl_.TryLock();
    }

    /*virtual*/ void Unlock()
    {
        mutex_impl_.Unlock();
    }


  private:
    MutexImpl mutex_impl_;
};

// Mutex и реализован как мютекс с предваряющей спин-блокировкой.
typedef Mutex SpinMutex;


////////////////////////////////////////////////////////////////////////////////


class RecursiveMutex : public RecursiveLockableResourceInterface,
                       private MutexImpl
{
  public:
    inline RecursiveMutex();
    inline ~RecursiveMutex();

    /*virtual*/ void Lock()
    {
        MutexImpl::Lock();
    }

    /*virtual*/ bool TryLock()
    {
        return MutexImpl::TryLock();
    }

    /*virtual*/ void Unlock()
    {
        MutexImpl::Unlock();
    }

  private:
#ifndef _WIN32
     pthread_mutexattr_t attr_;
#endif
};



#ifdef _WIN32


RecursiveMutex::RecursiveMutex()
{
    MutexImpl::DefaultInit();
}

RecursiveMutex::~RecursiveMutex()
{
    MutexImpl::DefaultDestroy();
}


#else


RecursiveMutex::RecursiveMutex()
{
    VERIFY(::pthread_mutexattr_init(&attr_) == 0);
    VERIFY(::pthread_mutexattr_settype(&attr_, PTHREAD_MUTEX_RECURSIVE) == 0);
    VERIFY(::pthread_mutex_init(&mutex_, &attr_) == 0);
}

RecursiveMutex::~RecursiveMutex()
{
    VERIFY(::pthread_mutex_destroy(&mutex_) == 0);
    VERIFY(::pthread_mutexattr_destroy(&attr_) == 0);
}


#endif  // _WIN32


////////////////////////////////////////////////////////////////////////////////


/** Нерекурсивный мютекс чтения/записи. */
class ReadWriteMutex : public ReadWriteLockableResourceInterface
{
  public:
    inline ReadWriteMutex();
    inline ~ReadWriteMutex();

    inline /*virtual*/ bool TryReadLock();
    inline /*virtual*/ void ReadLock();
    inline /*virtual*/ void ReadUnlock();

    inline /*virtual*/ bool TryWriteLock();
    inline /*virtual*/ void WriteLock();
    inline /*virtual*/ void WriteUnlock();

  private:
#ifdef _WIN32
    /** Использование нерекурсивного спина — оптимизация.
      * В данном случае оправданная (до 25%). */
    Spin internal_lock_;

    /** Readers wait on this if a writer has access. */
    HANDLE readers_sem_;

    /** Writers wait on this if a reader has access. */
    HANDLE writers_sem_;

    /** Number of readers waiting for access. */
    size_t num_waiting_readers_;

    /** Number of writers waiting for access. */
    size_t num_waiting_writers_;

    /** Number of threads currently with access.
      * 0 — no threads, >0 — number of readers, -1 - one writer). */
    int active_threads_;

    inline bool TryReadLock_nolock();
    inline bool TryWriteLock_nolock();
    inline void Unlock();

#else
    pthread_rwlock_t rw_mutex_;
#endif
};

#ifdef _WIN32

    ReadWriteMutex::ReadWriteMutex()
        : readers_sem_(::CreateSemaphore(NULL, 0, MAXLONG, NULL)),
          writers_sem_(::CreateSemaphore(NULL, 0, MAXLONG, NULL)),
          num_waiting_readers_(0),
          num_waiting_writers_(0),
          active_threads_(0)
    {
        VERIFY(readers_sem_ != NULL);
        VERIFY(writers_sem_ != NULL);
    }


    ReadWriteMutex::~ReadWriteMutex()
    {
        ::CloseHandle(readers_sem_);
        ::CloseHandle(writers_sem_);
    }


    bool ReadWriteMutex::TryReadLock()
    {
        Lock dnd(&internal_lock_);
        return TryReadLock_nolock();
    }


    void ReadWriteMutex::ReadLock()
    {
        Lock dnd(&internal_lock_);

        if ( TryReadLock_nolock() == false )
        {
            num_waiting_readers_++;
            dnd.Unlock();

            VERIFY(::WaitForSingleObject(readers_sem_,INFINITE)==WAIT_OBJECT_0);
        }
    }


    void ReadWriteMutex::ReadUnlock()
    {
        ReadWriteMutex::Unlock();
    }


    bool ReadWriteMutex::TryWriteLock()
    {
        Lock dnd(&internal_lock_);
        return TryWriteLock_nolock();
    }


    void ReadWriteMutex::WriteLock()
    {
        Lock dnd(&internal_lock_);

        if ( TryWriteLock_nolock() == false )
        {
            num_waiting_writers_++;
            dnd.Unlock();

            VERIFY(::WaitForSingleObject(writers_sem_,INFINITE)==WAIT_OBJECT_0);
        }
    }


    void ReadWriteMutex::WriteUnlock()
    {
        ReadWriteMutex::Unlock();
    }


    bool ReadWriteMutex::TryReadLock_nolock()
    {
        // Are there writers waiting or is a writer writing?
        const bool no_writers = num_waiting_writers_ == 0 &&
                                active_threads_ >= 0;

        if ( no_writers )
        {
            active_threads_++;
            return true;
        }

        return false;
    }


    bool ReadWriteMutex::TryWriteLock_nolock()
    {
        // Are there any threads accessing the resource?
        const bool resource_is_free = active_threads_ == 0;

        if ( resource_is_free )
        {
            active_threads_ = -1;
            return true;
        }

        return false;
    }


    void ReadWriteMutex::Unlock()
    {
        Lock dnd(&internal_lock_);

        // Readers have control so a reader must be done
        if ( active_threads_ > 0 )
            active_threads_--;
        else  // Writers have control so a writer must be done
            active_threads_++;

        HANDLE semaphore = NULL;
        LONG num_threads_to_wake_up = 0;

        if ( active_threads_ == 0 )
        {
            // Writers take priority over readers
            if ( num_waiting_writers_ > 0 )
            {
                active_threads_ = -1;
                num_waiting_writers_--;
                semaphore = writers_sem_;
                num_threads_to_wake_up = 1;
            }
            else if ( num_waiting_readers_ > 0 )
            {
                active_threads_ = static_cast<int>(num_waiting_readers_);
                num_waiting_readers_ = 0;
                semaphore = readers_sem_;
                num_threads_to_wake_up = active_threads_;
            }
        }

        dnd.Unlock();

        if ( semaphore != NULL )
        {
            VERIFY(::ReleaseSemaphore(
                semaphore, num_threads_to_wake_up, NULL) != FALSE);
        }
   }



#else

ReadWriteMutex::ReadWriteMutex()
{
    VERIFY(::pthread_rwlock_init(&rw_mutex_, NULL) == 0);
}


ReadWriteMutex::~ReadWriteMutex()
{
    VERIFY(::pthread_rwlock_destroy(&rw_mutex_) == 0);
}


bool ReadWriteMutex::TryReadLock()
{
    const int result = ::pthread_rwlock_tryrdlock(&rw_mutex_);

    if ( result == 0 )
        return true;

    if ( result == EBUSY )
        return false;

    VERIFY(result == 0 || result == EBUSY);
    return false;
}


void ReadWriteMutex::ReadLock()
{
    VERIFY(::pthread_rwlock_rdlock(&rw_mutex_) == 0);
}


bool ReadWriteMutex::TryWriteLock()
{
    const int result = ::pthread_rwlock_trywrlock(&rw_mutex_);

    if ( result == 0 )
        return true;

    if ( result == EBUSY )
        return false;

    VERIFY(result == 0 || result == EBUSY);
    return false;
}


void ReadWriteMutex::WriteLock()
{
    VERIFY(::pthread_rwlock_wrlock(&rw_mutex_) == 0);
}


void ReadWriteMutex::ReadUnlock()
{
    VERIFY(::pthread_rwlock_unlock(&rw_mutex_) == 0);
}


void ReadWriteMutex::WriteUnlock()
{
    VERIFY(::pthread_rwlock_unlock(&rw_mutex_) == 0);
}

#endif  // _WIN32

}  // libv

#endif  // LIBV_LOCK_MUTEX_H_
